package com.suray.basic.wcs.plc.core;


import com.suray.basic.wcs.plc.config.resolver.PlcAnnotation;
import com.suray.basic.wcs.plc.config.resolver.PlcInterceptor;
import com.suray.basic.wcs.plc.utils.LongConnSendUtil;
import com.suray.basic.wcs.plc.utils.NetAddress;
import com.suray.basic.wcs.utils.LoggerUtil;
import org.springframework.cglib.proxy.Enhancer;

import java.io.IOException;
import java.util.Arrays;

public class ModbusUtil {
    private LongConnSendUtil longConnSendUtil;
    private String plcName;

    public void setPlcName(String plcName) {
        this.plcName = plcName;
    }

    public String getPlcName() {
        return plcName;
    }

    protected ModbusUtil(LongConnSendUtil longConnSendUtil) {
        this.longConnSendUtil = longConnSendUtil;
    }

    public synchronized static ModbusUtil getModbus(LongConnSendUtil longConnSendUtil) {
        Enhancer plcEnhancer = new Enhancer();
        plcEnhancer.setSuperclass(ModbusUtil.class);
        plcEnhancer.setCallback(new PlcInterceptor());
        ModbusUtil modbusUtil = (ModbusUtil) plcEnhancer.create(new Class[]{LongConnSendUtil.class}, new Object[]{longConnSendUtil});
        return modbusUtil;
    }


    /**
     * 基础写操作
     *
     * @param no          地址位
     * @param oper_sign   需要写入的数值
     * @param des         指令功能描述
     * @param clazz       类
     * @param resendCount 重发次数
     * @return
     */
    @PlcAnnotation(value = "写PLC")
    public void oper(String des, Class<?> clazz, int no, int resendCount, int oper_sign) throws IOException {
        try {
            byte[] operBs = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00,
                    0x01};
            operBs[8] = (byte) ((no >> 8) & 0xff);
            operBs[9] = (byte) (no & 0xff);
            operBs[13] = (byte) ((oper_sign >> 8) & 0xff);
            operBs[14] = (byte) (oper_sign & 0xff);
            LoggerUtil.info(des + " 发送:" + Arrays.toString(operBs), this.getClass());
            byte[] resultBs = send(operBs);
            LoggerUtil.info(des + " 接收:" + Arrays.toString(resultBs), this.getClass());
            checkResult(operBs, resultBs);
        } catch (IOException e) {
            if (resendCount > 0) {
                LoggerUtil.info(des + "失败， 重发中...，剩余重发次数" + resendCount, clazz);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
                }
                oper(des, clazz, no, --resendCount, oper_sign);
            } else {
                throw new IOException();
            }
        }


    }


    /**
     * 顺序读取，从起始地址开始，连续读取若干位
     *
     * @param startNo     起始地址
     * @param num_query   读取位数
     * @param des         指令功能描述
     * @param clazz       类
     * @param resendCount 重发次数
     * @return
     */
    public int[] getResult(String des, Class<?> clazz, int startNo, int resendCount, int num_query) throws IOException {
        try {
            int[] result = new int[num_query];// 读取位数；0~num_query
            byte[] cmdByte = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, (byte) num_query};
            cmdByte[8] = (byte) ((startNo >> 8) & 0xff);
            cmdByte[9] = (byte) (startNo & 0xff);
            byte[] bs = send(cmdByte);
            int num = bs[5] & 0xff;
            int congZhanDiZhi = bs[6] & 0xff;
            int gongNengMa = bs[7] & 0xff;
            int ziJieShu = bs[8] & 0xff;
            int length = 3 + 2 * num_query;
            if (num == length && congZhanDiZhi == 1 && gongNengMa == 3 && ziJieShu == 2 * num_query) {
                for (int i = 0; i < num_query; i++) {
                    result[i] = (bs[9 + 2 * i] & 0xff) << 8 | (bs[10 + 2 * i] & 0xff);
                }
            } else {
                LoggerUtil.info("数据读取出错" + result.toString(), clazz);
            }
            return result;
        } catch (IOException e) {
            if (resendCount > 0) {
                LoggerUtil.info(des + "失败， 重发中...，剩余重发次数" + resendCount, clazz);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
                }

                return getResult(des, clazz, startNo, --resendCount, num_query);
            } else {
                throw new IOException();
            }
        }
    }

    /**
     * 基础写操作，从起始地址开始，连续写多位
     *
     * @param no            起始地址
     * @param oper_signList 连续写入内容
     * @param des           指令功能描述
     * @param clazz         类
     * @param resendCount   重发次数
     * @return
     */
    @PlcAnnotation(value = "写PLC")
    public void oper(String des, Class<?> clazz, int no, int resendCount, int... oper_signList) throws IOException {
        try {
            int size = oper_signList.length;
            int bsSize = 2 * size + 13;
            byte[] operBs = new byte[bsSize];
            operBs[5] = ((byte) (2 * size + 7));
            operBs[6] = 1;
            operBs[7] = 16;
            operBs[8] = ((byte) ((no >> 8) & 0xFF));
            operBs[9] = ((byte) (no & 0xFF));
            operBs[10] = ((byte) (size >> 8 & 0xFF));
            operBs[11] = ((byte) (size & 0xFF));
            operBs[12] = ((byte) (2 * size));

            for (int i = 0; i < size; i++) {
                int operSign = oper_signList[i];
                operBs[(13 + 2 * i)] = ((byte) ((operSign >> 8) & 0xFF));
                operBs[(14 + 2 * i)] = ((byte) ((operSign) & 0xFF));
            }
            LoggerUtil.info(des + " 发送:" + Arrays.toString(operBs), this.getClass());
            byte[] resultBs = send(operBs);
            LoggerUtil.info(des + " 接收:" + Arrays.toString(resultBs), this.getClass());
            checkResultList(operBs, resultBs);
        } catch (IOException e) {
            if (resendCount > 0) {
                LoggerUtil.info(des + "失败， 重发中...，剩余重发次数" + resendCount, clazz);

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
                }

                oper(des, clazz, no, --resendCount, oper_signList);
            } else {
                throw new IOException();
            }
        }
    }

    /**
     * 连续写入返回值验证
     *
     * @param cmdBs
     * @param resultBs
     */
    private void checkResultList(byte[] cmdBs, byte[] resultBs) {
        boolean correct = true;
        if (cmdBs == null || resultBs == null || cmdBs.length == 0 || resultBs.length < 12) {
            correct = false;
        } else if (resultBs[5] != (resultBs.length - 6)) {
            //数据可能丢失 或则 plc报错
            LoggerUtil.warn("plc通讯异常:" + Arrays.toString(resultBs), this.getClass());
            correct = false;
        } else {
            for (int i = 6; i < 12; i++) {
                if (resultBs[i] != cmdBs[i]) {
                    correct = false;
                    break;
                }
            }
        }
        if (!correct) {
            throw new RuntimeException("PLC返回值校验异常:" + Arrays.toString(resultBs));
        }
    }

    /**
     * 单点位写入返回值验证
     *
     * @param cmdBs
     * @param resultBs
     */
    private void checkResult(byte[] cmdBs, byte[] resultBs) {
        boolean correct = true;
        if (cmdBs == null || resultBs == null || cmdBs.length == 0 || resultBs.length < 8) {
            correct = false;
        } else if (resultBs[5] != (resultBs.length - 6)) {
            //数据可能丢失 或则 plc报错
            LoggerUtil.warn("plc通讯异常:" + Arrays.toString(resultBs), this.getClass());
            correct = false;
        } else {
            for (int i = 6; i < 12; i++) {
                if (resultBs[i] != cmdBs[i]) {
                    correct = false;
                    break;
                }
            }
        }
        if (!correct) {
            throw new RuntimeException("PLC返回值校验异常" + Arrays.toString(resultBs));
        }
    }

//    /**
//     * 构建Modbus读取数据的核心报文，需要指定其实地址位和目的地址位 默认功能码0x03
//     *
//     * @param startAddress 起始地址
//     * @param endAddress   目的地址
//     * @return
//     */

    /**
     * 判断需要分多少包读取
     * @param startAddress
     * @param endAddress
     * @return
     */
    public static int BuildReadModbusCommand(int startAddress, int endAddress) {
        int count = (endAddress - startAddress) / Plc.PLC_READ_MAX_NUM;
        return (endAddress - startAddress) % Plc.PLC_READ_MAX_NUM == 0 ? 1 : count + 1;

//        // 支持自动切割读取，字读取 120 个字，位读取 2000 个位
//        OperateResultExTwo<int[], int[]> bytes = HslHelper.SplitReadLength(mAddress.getAddress(), length,
//                (mAddress.getFunction() == ReadCoil || mAddress.getFunction() == ReadDiscrete) ? (short) 2000 : (short) 120);
//        for (int i = 0; i < bytes.Content1.length; i++) {
//            byte[] buffer = new byte[6];
//            buffer[0] = (byte) mAddress.getStation();
//            buffer[1] = (byte) mAddress.getFunction();
//            buffer[2] = Utilities.getBytes(bytes.Content1[i])[1];
//            buffer[3] = Utilities.getBytes(bytes.Content1[i])[0];
//            buffer[4] = Utilities.getBytes(bytes.Content2[i])[1];
//            buffer[5] = Utilities.getBytes(bytes.Content2[i])[0];
//            commands.add(buffer);


    }

    /**
     * 发送
     *
     * @param bs 字节数组
     * @return 字节数组
     */
    private byte[] send(byte[] bs) throws IOException {
        return longConnSendUtil.send(bs);
    }

    /**
     * 获取网络地址
     *
     * @return
     */
    public NetAddress getNetAddress() {
        return longConnSendUtil.getNetAddress();
    }
}